// TODO: Maybe split off create, train, and test from _GP/ to their own directories.


// STL
#include <memory>                   // std::unique_ptr<>
#include <string>                   // std::string
#include <tuple>                    // std::tie()
#include <utility>                  // std::move()
#include <vector>                   // std::vector<>

// Boost
#include <boost/serialization/export.hpp>

// jScience
#include "jScience/linalg.hpp"      // Matrix<>, Vector<>

// stats++
#include "statsxx/data.hpp"                              // read_datafile()
#include "statsxx/machine_learning/Gaussian_process.hpp" // Gaussian process stuff
#include "statsxx/statistics/covariance_functions.hpp"   // squared exponential

// this
#include "_GP.hpp"                  // create_GP(), train_GP(), test_GP()
#include "init.hpp"                 // read_param_file()

//BOOST_CLASS_EXPORT_GUID(SquaredExponential, "SE")
//BOOST_CLASS_EXPORT(CovarianceFunction);
//BOOST_CLASS_EXPORT(SquaredExponential);
//BOOST_CLASS_EXPORT_IMPLEMENT(CovarianceFunction);
//BOOST_CLASS_EXPORT_IMPLEMENT(SquaredExponential);
//BOOST_CLASS_EXPORT_IMPLEMENT(Gaussian_process::GaussianProcess);


//
// USAGE: gp filename_param
//
//     filename_param :: parameters filename
//
//     =========================================================
//
// NOTE: See the example input for parameters.
//
int main(
         int argc,
         char* argv[]
         )
{
    //=========================================================
    // INITIALIZATION
    //=========================================================

    std::string filename_param = std::string(argv[1]);

    // ----- PARAMETERS -----
    std::string gp_file;
    // =====
    bool        create;
    // -----
    int         ni;
    int         no;
    // -----
    std::string covariance_function;
    double      theta;
    // =====
    bool        train;
    // -----
    double      sigma_n;
    // -----
    bool        optimize_hyperparameters;
    // -----
    std::string train_X_in_file;
    std::string train_x_out_file;
    // =====
    bool        test;
    // -----
    std::string test_X_in_file;
    std::string test_x_out_file;
    // =====
    std::string prefix;
    std::tie(
             gp_file,
             // =====
             create,
             // -----
             ni,
             no,
             // =====
             covariance_function,
             theta,
             // =====
             train,
             // -----
             sigma_n,
             // -----
             optimize_hyperparameters,
             // -----
             train_X_in_file,
             train_x_out_file,
             // =====
             test,
             // -----
             test_X_in_file,
             test_x_out_file,
             // =====
             prefix
             ) = read_param_file(
                                 filename_param
                                 );

    //=========================================================
    // CREATE (OR READ) GP
    //=========================================================
    //
    // NOTE: This always occurs (create or read-in GP).

    std::unique_ptr<Gaussian_process::GaussianProcess> gp = create_GP(
                                                                      create,
                                                                      // -----
                                                                      ni,
                                                                      no,
                                                                      // -----
                                                                      covariance_function,
                                                                      theta,
                                                                      // -----
                                                                      gp_file
                                                                      );

    //=========================================================
    // TRAIN
    //=========================================================

    if( train )
    {
        //---------------------------------------------------------
        // GET DATA
        //---------------------------------------------------------

        Matrix<double> X_in  = read_datafile(
                                             train_X_in_file
                                             );

        Vector<double> x_out = read_datafile(
                                             train_x_out_file
                                             ).column(0);

        //---------------------------------------------------------
        // TRAIN
        //---------------------------------------------------------

        gp = train_GP(
                      std::move(gp),
                      // -----
                      sigma_n,
                      // -----
                      optimize_hyperparameters,
                      //------
                      X_in,
                      x_out,
                      // -----
                      gp_file
                      );
    }

    //=========================================================
    // TEST
    //=========================================================

    if(test)
    {

        //---------------------------------------------------------
        // GET DATA
        //---------------------------------------------------------

        Matrix<double> X_in  = read_datafile(
                                             test_X_in_file
                                             );

        Vector<double> x_out = read_datafile(
                                             test_x_out_file
                                             ).column(0);

        //---------------------------------------------------------
        // TEST
        //---------------------------------------------------------

        test_GP(
                std::move(gp),
                // -----
                X_in,
                x_out,
                // -----
                prefix
                );
    }

    return 0;
}

